package client

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"gitee.com/openeuler/go-gitee/gitee"
	"github.com/antihax/optional"
	"io/ioutil"
	"log"
	"net/http"
	"openeuler.org/cir/cmd"
	"openeuler.org/cir/models"
	"regexp"
	"sync"
	"time"
)

const (
	perPage    = 20
	IssueState = "closed"
	IssueType  = "CVE和安全问题"
)

var getCveDetailUrl = "http://cve.openeuler.org/cve-security-notice-server/cvedatabase/getByCveId?cveId=%s"

type Client struct {
	AccessToken string //gitee accessToken
	Org         string //gitee organization namespace Prefix suffix
	StartTime   int64  //issue 抓取开始时间
	EndTime     int64  //issue 抓取结束时间
	Context     context.Context
	GiteeClient *gitee.APIClient
	ExcelFile   *Excel
	sync.WaitGroup
}

func Init(cmd cmd.Command) (c *Client, err error) {
	if cmd.AccessToken == "" || cmd.Org == "" {
		return nil, errors.New("initialization parameter error")
	}
	st, err := time.Parse("2006-01-02 ", cmd.StartTime)
	if err != nil {
		return nil, errors.New("initialization parameter error ")
	}
	et, err := time.Parse("2006-01-02 ", cmd.EndTime)
	if err != nil {
		return nil, errors.New("initialization parameter error ")
	}
	c = &Client{AccessToken: cmd.AccessToken, Org: cmd.Org, StartTime: st.Unix(), EndTime: et.Unix()}
	ctx := context.Background()
	//ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: accessToken})
	giteeConf := gitee.NewConfiguration()
	//giteeConf.HTTPClient = oauth2.NewClient(ctx, ts)
	giteeConf.HTTPClient = &http.Client{}
	giteeClient := gitee.NewAPIClient(giteeConf)
	c.GiteeClient = giteeClient
	c.Context = ctx
	excelName := fmt.Sprintf("Cve和安全公告%s至%s.xlsx", cmd.StartTime, cmd.EndTime)
	excel, err := GenerateExcel(excelName)
	if err != nil {
		return nil, err
	}
	excel.SecNoticePrefix = cmd.AnnPrefix
	excel.SecNoticeSuffixIdx = cmd.AnnSuffix
	c.ExcelFile = &excel
	return c, nil
}

func (c *Client) Run() {
	org, err := c.GetOrgInfo()
	if err != nil {
		fmt.Println(err, ":获取码云组织信息失败，请检查输入的组织名与AccessToken!")
		return
	}
	reposNum := org.PublicRepos + org.PrivateRepos
	if reposNum <= 0 {
		fmt.Println("可扫描仓库数为0，程序运行结束...")
		return
	}
	fmt.Printf("开始扫描：本次将扫描%d个仓库\n", reposNum)
	pageSize := reposNum / int64(perPage)
	if reposNum%int64(perPage) > 0 {
		pageSize = pageSize + 1
	}
	pathList := make(chan []string, pageSize/100)
	var i int64
	for i = 1; i <= pageSize; i++ {
		c.Add(1)
		go c.GetOrgRepos(i, pathList)
	}
	for i = 1; i <= pageSize; i++ {
		pl := <-pathList
		fmt.Printf("第%d次仓库path(%d):%v\n", i, len(pl), pl)
		c.Add(1)
		go c.GetReposIssues(pl)

	}
	c.Wait()
}

func (c *Client) GetReposIssues(repos []string) {
	defer c.Done()
	if len(repos) <= 0 {
		return
	}
	var notFindRep []string
	var lps []models.Loophole
	for _, v := range repos {
		issues, notFind := c.GetRepIssues(v)
		lps = append(lps, issues...)
		notFindRep = append(notFindRep, notFind...)
	}
	if notFindRep != nil && len(notFindRep) > 0 {
		//fmt.Println("some repos issues request 404: ",notFindRep)
	}
	if lps != nil && len(lps) > 0 {
		//fmt.Printf("loophole：%#v\n",lps)
		err := c.ExcelFile.SetExcelRowsValue(c.ExcelFile.ParseLoopholeToExcelValues(lps))
		if err != nil {
			log.Println(err)
		}
	}

}

func (c *Client) GetRepIssues(path string) (resIssue []models.Loophole, notFind []string) {
	haveNext := true
	page := int32(1)
	for haveNext {
		param := gitee.GetV5ReposOwnerRepoIssuesOpts{
			//AccessToken: optional.NewString(c.AccessToken),
			State:     optional.NewString(IssueState),
			Page:      optional.NewInt32(page),
			PerPage:   optional.NewInt32(int32(perPage)),
			Sort:      optional.NewString("created"),
			Direction: optional.NewString("desc"),
		}
		issues, _, err := c.GiteeClient.IssuesApi.GetV5ReposOwnerRepoIssues(c.Context, c.Org, path, &param)
		if err != nil {
			notFind = append(notFind, path)
			haveNext = false
		}
		if len(issues) < perPage {
			haveNext = false
		}
		if len(issues) > 0 {
			for _, issue := range issues {
				if issue.IssueType == IssueType && c.judgeIssueTime(issue.FinishedAt) {
					lp, ok := IssueToLoopHole(issue.Body)
					if ok && lp.Number != "" && isCvePublic(lp.Number) {
						branch := getRelatedBranch(c.Org, path, issue.Number)
						lp.InfProduct = branch
						resIssue = append(resIssue, lp)
					}
				}
			}
		}
		if haveNext {
			page++
		}
	}
	if len(resIssue) > 0 {
		fmt.Println(path, "：issue 解析完成")
		//获取产品分支
		/*params := gitee.GetV5ReposOwnerRepoBranchesOpts{
			AccessToken: optional.NewString(c.AccessToken),
		}
		branches, _, err := c.GiteeClient.RepositoriesApi.GetV5ReposOwnerRepoBranches(c.Context, c.Org, path, &params)
		infProduct := ""
		if err != nil {
			log.Println(err)
		} else {
			for _, v := range branches {
				if strings.Contains(v.Name, "LTS") {
					infProduct = v.Name
				}
			}
		}*/
		description := GetRepoDescription(path)
		for k, _ := range resIssue {
			//resIssue[k].InfProduct = infProduct
			resIssue[k].Repo = path
			resIssue[k].RepoDesc = description
		}
	} else {
		fmt.Println(path, "：Does not contain eligible CVE and security issues")
	}
	return
}

func (c *Client) GetOrgRepos(pageIndex int64, in chan<- []string) {
	defer c.Done()
	params := gitee.GetV5OrgsOrgReposOpts{AccessToken: optional.NewString(c.AccessToken),
		Page:    optional.NewInt32(int32(pageIndex)),
		PerPage: optional.NewInt32(int32(perPage)),
	}
	repos, _, err := c.GiteeClient.RepositoriesApi.GetV5OrgsOrgRepos(c.Context, c.Org, &params)
	if err != nil {
		fmt.Printf("加载第%d页数据出错:%v", pageIndex, err)
		return
	}

	if len(repos) > 0 {
		var repList []string
		for _, v := range repos {
			repList = append(repList, v.Path)
		}
		in <- repList
	}

}

func (c *Client) GetOrgInfo() (orgInfo *models.OrgInfo, err error) {
	url := `https://gitee.com/api/v5/orgs/%s?access_token=%s`
	resp, e := http.Get(fmt.Sprintf(url, c.Org, c.AccessToken))
	if e != nil {
		return nil, e
	}
	if resp.StatusCode != 200 {
		return nil, errors.New("请求失败")
	}
	defer resp.Body.Close()
	body, e := ioutil.ReadAll(resp.Body)
	if e != nil {
		return nil, e
	}
	org := &models.OrgInfo{}
	err = json.Unmarshal(body, org)
	if err != nil {
		return nil, err
	}
	fmt.Printf("orgInfo:%v\n", *org)
	return org, nil
}

func GetRepoDescription(repo string) (desc string) {
	if repo == "" {
		return ""
	}
	url := fmt.Sprintf("https://api.openeuler.org/pkgmanagedebug/packages/packageInfo?table_name=mainline&pkg_name=%s", repo)
	resp, err := http.Get(url)
	if err != nil {
		return ""
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return ""
	}
	var pkg models.PackageInfo
	err = json.Unmarshal(body, &pkg)
	if err != nil {
		return ""
	}
	if pkg.Code == "2001" {
		return pkg.Data.Description
	}
	return ""
}

func (c *Client) judgeIssueTime(finishAt time.Time) bool {
	/*parse, err := time.Parse(time.RFC3339, finishAt)
	if  err != nil {
		return false
	}*/
	ft := finishAt.Unix()
	if ft >= c.StartTime && ft < c.EndTime {
		return true
	} else {
		return false
	}
}

func isCvePublic(cveNum string) bool {
	if GetCveSecurityNotice(cveNum) {
		fmt.Println(cveNum, "已导出到cve官网^……^")
		return true
	} else {
		return false
	}
}

//GetSecurityNotice Go to the CVE official website to obtain the cve data to determine
//whether the cve issue needs to be exported.
func GetCveSecurityNotice(cveNumber string) bool {
	resp, err := http.Get(fmt.Sprintf(getCveDetailUrl, cveNumber))
	if err != nil {
		fmt.Println(err)
		return false
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		return false
	}
	var detail models.RespCveDetail
	err = json.Unmarshal(body, &detail)
	if err != nil {
		fmt.Println(err)
		return false
	}
	if detail.Result != nil && detail.Result.Id > 0 {
		return true
	}
	return false

}

func getRelatedBranch(owner, repo, issueNum string) (branch string) {
	url := fmt.Sprintf("https://gitee.com/%s/%s/issues/%s?from=project-issue", owner, repo, issueNum)
	resp, err := http.Get(url)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	regexpBranch := regexp.MustCompile(`<input id="issue_branch" name="issue\[branch\]" type="hidden"(.*?)/>`)
	find := regexpBranch.Find(body)
	findStr := string(find)
	if len(find) > 0 {
		regexpBranch = regexp.MustCompile(`value="refs/heads/(.*?)"`)
		match := regexpBranch.FindAllStringSubmatch(findStr, -1)
		if len(match)>0&& len(match[0])>1 {
			branch = match[0][1]
		}
	}
	return
}
